package com.usian.paymentdemo.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.gson.Gson;
import com.usian.paymentdemo.config.WxPayConfig;
import com.usian.paymentdemo.entity.OrderInfo;
import com.usian.paymentdemo.entity.PaymentInfo;
import com.usian.paymentdemo.entity.RefundInfo;
import com.usian.paymentdemo.enums.OrderStatus;
import com.usian.paymentdemo.enums.wxpay.WxApiType;
import com.usian.paymentdemo.enums.wxpay.WxNotifyType;
import com.usian.paymentdemo.enums.wxpay.WxRefundStatus;
import com.usian.paymentdemo.enums.wxpay.WxTradeState;
import com.usian.paymentdemo.service.OrderInfoService;
import com.usian.paymentdemo.service.PaymentInfoService;
import com.usian.paymentdemo.service.RefundInfoService;
import com.usian.paymentdemo.service.WxPayService;
import com.wechat.pay.java.core.certificate.model.DownloadCertificateResponse;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.http.*;
import com.wechat.pay.java.core.util.IOUtil;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.payments.nativepay.model.*;
import com.wechat.pay.java.service.payments.nativepay.model.Amount;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {

    @Autowired
    private WxPayConfig wxPayConfig;

    @Autowired
    private NativePayService nativePayService;

    @Autowired
    private RefundService refundService;

    @Autowired
    private OrderInfoService orderInfoService;

    @Autowired
    private PaymentInfoService paymentInfoService;


    @Override
    public Map<String, Object> nativePayV3(Long productId) {

        log.info("生成订单");
        OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId);
        String codeUrl = orderInfo.getCodeUrl();
        if (orderInfo != null && !StringUtils.isEmpty(codeUrl)) {
            log.info("订单已存在，二维码已保存");
            //返回二维码
            Map<String, Object> map = new HashMap<>();
            map.put("codeUrl", codeUrl);
            map.put("orderNo", orderInfo.getOrderNo());
            return map;
        }
        // request.setXxx(val)设置所需参数，具体参数可见Request定义
        PrepayRequest request = new PrepayRequest();
        request.setAppid(wxPayConfig.getAppid());
        request.setMchid(wxPayConfig.getMchId());

        // 标题
        request.setDescription(orderInfo.getTitle());
        request.setNotifyUrl(wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));
        request.setOutTradeNo(orderInfo.getOrderNo());
        Amount amount = new Amount();
        amount.setTotal(orderInfo.getTotalFee());
        amount.setCurrency("CNY");
        request.setAmount(amount);
        // 调用下单方法，得到应答
        PrepayResponse response = nativePayService.prepay(request);
        System.out.println(response.getCodeUrl());

        //保存二维码
        String orderNo = orderInfo.getOrderNo();
        orderInfoService.saveCodeUrl(orderNo, response.getCodeUrl());

        Map<String, Object> map = new HashMap<>();
        map.put("codeUrl", response.getCodeUrl());
        map.put("orderNo", orderInfo.getOrderNo());
        return map;
    }


    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void processOrder(Transaction transaction) throws Exception {
        String tradno = transaction.getOutTradeNo(); //对比数据库中 order_no 确保是一个订单
        String tradstate = transaction.getTradeState().toString(); // SUCCESS
        Integer total = transaction.getAmount().getTotal(); // 应付金额
        Integer userTotal = transaction.getAmount().getPayerTotal(); // 用户支付金额

        //加锁
        /*在对业务数据进行状态检查和处理之前，
        要采用数据锁进行并发控制，
        以避免函数重入造成的数据混乱*/
        //尝试获取锁：
        // 成功获取则立即返回true，获取失败则立即返回false。不必一直等待锁的释放
        if (lock.tryLock()) {
            try {
                // 查询订单表
                OrderInfo info = orderInfoService.getOrderInfoByNoId(tradno);
                if (!OrderStatus.NOTPAY.getType().equals(info.getOrderStatus())) {
                    return;
                }

                if (!info.getTotalFee().equals(total) || !info.getTotalFee().equals(userTotal)) { //证明支付金额保持一致
                    throw new Exception("金额不一致");
                }
                if (!tradstate.equals("SUCCESS")) {
                    throw new Exception("支付状态不一致");
                }
                // 全部没问题  修改订单状态
                orderInfoService.updateStatusByOrderNo(tradno, OrderStatus.SUCCESS);
                paymentInfoService.createPaymentInfo(transaction);
            } finally {
                //执行完主动释放锁
                lock.unlock();
            }
        }
    }


    /**
     * 用户取消订单
     *
     * @param orderNo
     */
    @Override
    public void cancelOrder(String orderNo) throws Exception {
        //调用微信支付的关单接口
        this.closeOrder(orderNo);
        //更新商户端的订单状态
        orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CANCEL);
    }


    private void closeOrder(String orderNo) {
        CloseOrderRequest closeRequest = new CloseOrderRequest();
        closeRequest.setMchid(wxPayConfig.getMchId());
        closeRequest.setOutTradeNo(orderNo);
        nativePayService.closeOrder(closeRequest);
    }


    /**
     * 查询订单
     *
     * @param orderNo
     * @return
     * @throws Exception
     */
    @Override
    public String queryOrder(String orderNo) throws Exception {
        PaymentInfo paymentInfo = paymentInfoService.getOne(
                Wrappers.<PaymentInfo>lambdaQuery().eq(
                        PaymentInfo::getOrderNo, orderNo
                )
        );
        Gson gson = new Gson();
        Transaction result = null;
        try {
            QueryOrderByIdRequest queryRequest = new QueryOrderByIdRequest();
            queryRequest.setMchid(wxPayConfig.getMchId());
            queryRequest.setTransactionId(paymentInfo.getTransactionId());
            result = nativePayService.queryOrderById(queryRequest);
            System.out.println(result.getTradeState());
        } catch (ServiceException e) {
            // API返回失败, 例如ORDER_NOT_EXISTS
            System.out.printf("code=[%s], message=[%s]\n", e.getErrorCode(), e.getErrorMessage());
            System.out.printf("reponse body=[%s]\n", e.getResponseBody());
        }
        return gson.toJson(result);
    }

    @Override
    public void checkOrderStatus(String orderNo) throws Exception {
        log.warn("根据订单号核实订单状态 ===> {}", orderNo);
        //调用微信支付查单接口
        String result = this.queryOrder(orderNo);
        Gson gson = new Gson();
        Map resultMap = gson.fromJson(result, HashMap.class);
        //获取微信支付端的订单状态
        Object tradeState = resultMap.get("trade_state");
        //判断订单状态
        if (WxTradeState.SUCCESS.getType().equals(tradeState)) {
            log.warn("核实订单已支付 ===> {}", orderNo);
            //如果确认订单已支付则更新本地订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
            //记录支付日志
            Transaction transaction = gson.fromJson(result, Transaction.class);
            paymentInfoService.createPaymentInfo(transaction);
        }
        if (WxTradeState.NOTPAY.getType().equals(tradeState)) {
            log.warn("核实订单未支付 ===> {}", orderNo);
            //如果订单未支付，则调用关单接口
            this.closeOrder(orderNo);
            //更新本地订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CLOSED);
        }

    }

    @Resource
    private RefundInfoService refundsInfoService;

    /**
     * 退款
     *
     * @param orderNo
     * @param reason
     * @throws IOException
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void refund(String orderNo, String reason) throws Exception {
        log.info("创建退款单记录");
        //根据订单编号创建退款单
        RefundInfo refundsInfo = refundsInfoService.createRefundByOrderNo(orderNo, reason);
        log.info("调用退款API");
        CreateRequest createRequest = new CreateRequest();
        createRequest.setOutTradeNo(orderNo); // 订单编号
        // 退款单编号
        createRequest.setOutRefundNo(refundsInfo.getRefundNo());
        // 退款原因
        createRequest.setReason(reason);
        // 退款通知接口
        createRequest.setNotifyUrl(wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));
        // 退款金额
        AmountReq amountReq = new AmountReq();
        amountReq.setCurrency("CNY");
        amountReq.setRefund(refundsInfo.getRefund().longValue());
        amountReq.setTotal(refundsInfo.getTotalFee().longValue());
        createRequest.setAmount(amountReq);

        Refund refunds = refundService.create(createRequest);
        if (refunds != null && refunds.getRefundId() != null) {

            //更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo,
                    OrderStatus.REFUND_PROCESSING);
            Gson gson = new Gson();
            String bodyAsString = gson.toJson(refunds);
            //更新退款单
            refundsInfoService.updateRefund(bodyAsString);
            log.info("成功, 退款返回结果 = " + bodyAsString);
        } else {
            log.info("失败");
        }
    }

    @Override
    public String queryRefund(String refundNo) throws Exception {
        log.info("查询退款接口调用 ===> {}", refundNo);

        QueryByOutRefundNoRequest queryByOutRefundNoRequest = new QueryByOutRefundNoRequest();
        queryByOutRefundNoRequest.setOutRefundNo(refundNo);
        Refund refund = refundService.queryByOutRefundNo(queryByOutRefundNoRequest);
        String bodyAsString = null;
        if (refund != null) {
            Gson gson = new Gson();
            bodyAsString = gson.toJson(refund);
            log.info("成功, 查询退款返回结果 = " + bodyAsString);
        } else {
            log.info("退款查询失败");
        }
        return bodyAsString;
    }

    @Override
    public void checkRefundStatus(String refundNo) throws Exception {
        log.warn("根据退款单号核实退款单状态 ===> {}", refundNo);
        //调用查询退款单接口
        String result = this.queryRefund(refundNo);
        //组装json请求体字符串
        Gson gson = new Gson();
        Map<String, String> resultMap = gson.fromJson(result, HashMap.class);
        //获取微信支付端退款状态
        String status = resultMap.get("status");
        String orderNo = resultMap.get("out_trade_no");
        if (WxRefundStatus.SUCCESS.getType().equals(status)) {
            log.warn("核实订单已退款成功 ===> {}", refundNo);
            //如果确认退款成功，则更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo,
                    OrderStatus.REFUND_SUCCESS);
            //更新退款单
            refundsInfoService.updateRefund(result);
        }
        if (WxRefundStatus.ABNORMAL.getType().equals(status)) {
            log.warn("核实订单退款异常 ===> {}", refundNo);
            //如果确认退款成功，则更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo,
                    OrderStatus.REFUND_ABNORMAL);
            //更新退款单
            refundsInfoService.updateRefund(result);
        }

    }

    @Override
    public void processRefund(RefundNotification refundNotification) throws Exception {
        log.info("退款单");
        String tradno = refundNotification.getOutTradeNo(); // 商户订单编号
        String status = refundNotification.getRefundStatus().toString(); // 状态
        Long refund = refundNotification.getAmount().getRefund(); //退款金额
        Long total = refundNotification.getAmount().getTotal(); //订单金额
        Gson gson = new Gson();
        if (lock.tryLock()) {
            try {
                // 查询订单表
                OrderInfo info = orderInfoService.getOrderInfoByNoId(tradno);
                if (!OrderStatus.NOTPAY.getType().equals(info.getOrderStatus())) {
                    return;
                }
                // 查询退款单
                RefundInfo refundInfo =  refundsInfoService.getRefundInfoByOrderNo(tradno);
                if(!total.equals(refundInfo.getTotalFee().longValue()) || !refund.equals(refundInfo.getRefund().longValue() )){
                    throw new Exception("金额不一致");
                }
                if(!status.equals("SUCCESS")){
                    throw new Exception("支付状态不一致");
                }
                //更新订单状态
                orderInfoService.updateStatusByOrderNo(tradno, OrderStatus.REFUND_SUCCESS);
                //更新退款单
                refundsInfoService.updateRefund(gson.toJson(refundNotification));
            } finally {
                //要主动释放锁
                lock.unlock();
            }
        }

    }


    @Autowired
    HttpClient httpClient;


    @Override
    public String queryBill(String billDate, String type) throws Exception {

        String url = "";
        if("tradebill".equals(type)){
            url = WxApiType.TRADE_BILLS.getType();
        }else if("fundflowbill".equals(type)){
            url = WxApiType.FUND_FLOW_BILLS.getType();
        }else{
            throw new RuntimeException("不支持的账单类型");
        }
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.addHeader("Accept", "application/json");
        //billDate =  URLEncoder.encode(billDate, "UTF-8");
        HttpResponse<Map> httpResponse =  httpClient.get(httpHeaders, wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate),Map.class);
        System.out.println(httpResponse.getBody());
        return (String) httpResponse.getServiceResponse().get("download_url");
    }

    @Override
    public String downloadBill(String billDate, String type) throws Exception {
        log.warn("下载账单接口调用 {}, {}", billDate, type);
        //获取账单url地址
        String downloadUrl = this.queryBill(billDate, type);
        InputStream inputStream = httpClient.download(downloadUrl);
        // 非压缩的账单可使用 core.util.IOUtil 从流读入内存字符串，大账单请慎用
        String respBody = IOUtil.toString(inputStream);
        inputStream.close();
        return respBody;
    }

}


